<?php
/**
 * @copyright Arthur Beer
 * @package core.exception
 *
 * @copyright TinyWEB CMS
 * @link http://tinywebcms.ru
 * @link http://cms.vingrad.ru
 * @version SVN: $Id$ 
 */
 
/**
 * class ErrorCatcher
 * 
 */
class ErrorCatcher
{
    
    const CONTEXT_RADIUS  = 5;
    
    const MODE_DEBUG      = 'DEBUG';
    const MODE_PRODUCTION = 'PRODUCTION';
    
    const DECO_HTML = 'Html';
    const DECO_CLI  = 'Cli';
    
    protected $error_page = '_503';    
    protected $mode;
    protected $decorator;
    
    private $debug_modes  = array('jail');
    
    function __construct()
    {
    	define('APP_MODE', 'jail');
        if (defined('APP_MODE') && in_array(APP_MODE, $this->debug_modes)) {
            $this->mode = self::MODE_DEBUG;
            if(php_sapi_name() == 'cli') {
                $this->decorator = self::DECO_CLI;
            } else {
                $this->decorator = self::DECO_HTML;
            }
        } else {
            $this->mode = self::MODE_PRODUCTION;
        }
        //$this->mode = self::MODE_PRODUCTION;
    }
    
    function init()
    {
        if($this->mode == self::MODE_DEBUG){
            self::registerExceptionHandler($this, 'handleException');
        }
    }
    
    function handleException($e)
    {   
        $error = array(
            'message' => $e->getMessage(),
            'code'    => $e->getCode(),
            'line'    => $e->getLine(),
            'file'    => $e->getFile(),
            'trace'   => $e->getTraceAsString(),
        );
        $this->doDisplay($error);
    }
    
    function handleError()
    {
        $error_data = func_get_args();
        $error = array(
            'message' => $error_data[1],
            'code'    => $error_data[0],
            'line'    => $error_data[3],
            'file'    => $error_data[2],
            'trace'   => '',
        );        
        $this->doDisplay($error);
    }
    
    static function registerExceptionHandler()
    {
        $delegate = func_get_args();
        set_exception_handler($delegate);
    }
    
    static function registerErrorHandler()
    {
        $delegate = func_get_args();
        set_error_handler($delegate);
    }
    
    private function collectErrorInfo(& $error)
    {
        $error['context']  = $this->getFileContext($error['file'], $error['line']);
        $error['request']  = $this->getRequest();
        $error['session']  = $this->getSession();
    }
    
    private function getRequest()
    {
        $method = (Env::Server('REQUEST_METHOD', false)) ? (Env::Server('REQUEST_METHOD', false).': ') : '';
        $uri    = (Env::Server('SCRIPT_URI', ''));
        $query  = (Env::Server('QUERY_STRING', false)) ? ('?'.Env::Server('QUERY_STRING')) : '';
        return $method . $uri . $query;
    }
    
    private function getSession()
    {
        return '';
    }
    
    private function doDisplay($error)
    {
        $this->collectErrorInfo($error);
        $decorator_name  = 'ErrorDecorator' . $this->decorator;
        $decorator = new $decorator_name($error);
        $decorator->exec();
        exit;
    }
    
    private function getFileContext($file, $line_number)
    {
        $context = array();
        $i = 0;
        foreach(file($file) as $line) {
            $i++;
            if($i >= $line_number - self::CONTEXT_RADIUS && $i <= $line_number + self::CONTEXT_RADIUS) {
                if ($i == $line_number) {
                    $context[] = ' >>'. $i ."\t". $line;
                }else{
                    $context[] = '   '. $i ."\t". $line;
                }
            }
            if($i > $line_number + self::CONTEXT_RADIUS) break;
        }
        return "\n". implode("", $context);
    }
} 


class ErrorDecoratorCli
{
    private $error = array();
    
    function __construct($error)
    {
        $this->error = $error;
    }
    
    public function exec()
    {
        print $this->decorate();
    }
    
    private function decorate()
    {
        $error = $this->prepareErrorData($this->error);
        $body = <<<EOD
        
    {$error['message']}

Error in '{$error['file']}' around line {$error['line']}: {$error['context']}

Call stack: {$error['trace']}
EOD;
        return $body;
    }
    
    private function prepareErrorData()
    {
        $error = array();
        foreach ($this->error as $key=>$value){
            $error[$key] = $value;
        }
        $error['message'] = $error['message'];
        return $error;
    }
}


class ErrorDecoratorHtml
{
    
    private $error = array();
    
    function __construct($error)
    {
        $this->error = $error;
    }
    
    private function prepareErrorData()
    {
        $error = array();
        foreach ($this->error as $key=>$value){
            $error[$key] = htmlspecialchars($value);
        }
        $error['message'] = nl2br($error['message']);
        return $error;
    }
    
    function exec()
    {
        for($i=0; $i < ob_get_level(); $i++) {
            ob_end_clean();
        }
        print $this->decorate();
    }

    private function decorate()
    {
        $error = $this->prepareErrorData($this->error);
        $title = strip_tags($error['message']);
        $body = <<<EOD
<html>
<head>
  <title>{$title}</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <style>
    body { background-color: #fff; color: #333; }
    body, p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 25px; }
    pre { background-color: #eee; padding: 10px; font-size: 11px; line-height: 18px; }
    a { color: #000; }
    a:visited { color: #666; }
    a:hover { color: #fff; background-color:#000; }
  </style>
  <script>
  function TextDump() {
    w = window.open('', "Error text dump", "scrollbars=yes,resizable=yes,status=yes,width=1000px,height=800px,top=100px,left=100px");
    w.document.write('<html><body>');
    w.document.write('<h1>' + document.getElementById('Title').innerHTML + '</h1>');
    w.document.write(document.getElementById('Context').innerHTML);
    w.document.write(document.getElementById('Trace').innerHTML);
    w.document.write(document.getElementById('Request').innerHTML);
    w.document.write(document.getElementById('Session').innerHTML);
    w.document.write('</body></html>');
    w.document.close();
  }
  </script>
</head>
<body>
<h2 id='Title'>{$error['message']}</h2><a href="#" onclick="TextDump(); return false;">Raw dump</a>
<div id="Context" style="display: block;"><h3>Error in '{$error['file']}' around line {$error['line']}:</h3><pre>{$error['context']}</pre></div>
<div id="Trace"><h2>Call stack</h2><pre>{$error['trace']}</pre></div>
<div id="Request"><h2>Request</h2><pre>{$error['request']}</pre></div>
<div id="Session"><h2>Session</h2><pre>{$error['session']}</pre></div>
</body>
</html>
EOD;
        return $body;
    }
}

?>